package licence

import (
	"bytes"
	"errors"
	"log"
	"net"
	"os"
	"os/exec"
	"runtime"
	"strings"
)

// Linux:获取系统序列号
func getLinuxSerialNumber() (serialNumber string, err error) {
	cmd := exec.Command("dmidecode", "-s", "system-serial-number")

	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = os.Stderr

	err = cmd.Start()
	if err != nil {
		return "", err
	}
	err = cmd.Wait()
	if err == nil {
		serial_number := out.String()
		serial_number = strings.Replace(serial_number, "\n", "", -1)
		return serial_number, nil
	} else {
		return "", err
	}
}

// Linux:获取系统UUID
func getLinuxPlatformUUID() (UUID string, err error) {
	cmd := exec.Command("dmidecode", "-s", "system-uuid")

	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = os.Stderr

	err = cmd.Start()
	if err != nil {
		return "", err
	}
	err = cmd.Wait()
	if err == nil {
		uuid := out.String()
		uuid = strings.Replace(uuid, "\n", "", -1)
		return uuid, nil
	} else {
		return "", err
	}
}

// Linux:获取磁盘序列号
func getLinuxDiskSN() (serialNumber string, err error) {
	cmd := exec.Command("lsblk", "--nodeps", "-no", "serial", "/dev/sda")

	var out bytes.Buffer
	cmd.Stdout = &out
	cmd.Stderr = os.Stderr

	err = cmd.Start()
	if err != nil {
		return "", err
	}
	err = cmd.Wait()
	if err == nil {
		disk_sn := out.String()
		disk_sn = strings.Replace(disk_sn, "\n", "", -1)
		return disk_sn, nil
	} else {
		return "", err
	}
}

// Linux:获取CPUID
func getLinuxCpuId() (cpuId string, err error) {
	cmds := []*exec.Cmd{
		exec.Command("dmidecode", "-t", "processor"),
		exec.Command("grep", "ID"),
		exec.Command("head", "-1"),
	}
	cpuId, err = execPipeLine(cmds...)
	cpuId = strings.TrimSpace(cpuId)
	cpuId = strings.Replace(cpuId, "ID: ", "", -1)
	cpuId = strings.Replace(cpuId, "\t", "", -1)
	cpuId = strings.Replace(cpuId, "\n", "", -1)
	cpuId = strings.Replace(cpuId, " ", "-", -1)
	return cpuId, err
}

// 获取docker容器ID
func getDockerContainerID() (containerId string, err error) {
	cmds := []*exec.Cmd{
		exec.Command("head", "-1", "/proc/self/cgroup"),
		exec.Command("cut", "-d/", "-f3"),
	}
	containerId, err = execPipeLine(cmds...)
	return containerId, err
}

// 执行多条指令-入口函数
func execPipeLine(cmds ...*exec.Cmd) (string, error) {
	output, stderr, err := pipeline(cmds...)
	if err != nil {
		return "", err
	}

	if len(output) > 0 {
		return string(output), nil
	}

	if len(stderr) > 0 {
		return string(stderr), nil
	}
	return "", errors.New("no returns")
}

// 执行多条指令
func pipeline(cmds ...*exec.Cmd) (pipeLineOutput, collectedStandardError []byte, pipeLineError error) {
	if len(cmds) < 1 {
		return nil, nil, nil
	}

	var output bytes.Buffer
	var stderr bytes.Buffer

	last := len(cmds) - 1
	for i, cmd := range cmds[:last] {
		var err error
		if cmds[i+1].Stdin, err = cmd.StdoutPipe(); err != nil {
			return nil, nil, err
		}
		cmd.Stderr = &stderr
	}

	cmds[last].Stdout, cmds[last].Stderr = &output, &stderr

	for _, cmd := range cmds {
		if err := cmd.Start(); err != nil {
			return output.Bytes(), stderr.Bytes(), err
		}
	}

	for _, cmd := range cmds {
		if err := cmd.Wait(); err != nil {
			return output.Bytes(), stderr.Bytes(), err
		}
	}

	return output.Bytes(), stderr.Bytes(), nil
}

// Windows:获取系统序列号
func getWindowsSerialNumber() (serialNumber string, err error) {
	cmd := exec.Command("wmic", "baseboard", "get", "serialnumber")
	b, e := cmd.CombinedOutput()
	if e == nil {
		serialNumber = string(b)
		serialNumber = serialNumber[12 : len(serialNumber)-2]
		serialNumber = strings.ReplaceAll(serialNumber, "\n", "")
		serialNumber = strings.ReplaceAll(serialNumber, " ", "")
		serialNumber = strings.ReplaceAll(serialNumber, "\r", "")
	} else {
		return "", nil
	}
	return serialNumber, nil
}

// Windows:获取UUID
func getWindowsPlatformUUID() (uuid string, err error) {
	cmd := exec.Command("wmic", "csproduct", "get", "uuid")
	b, e := cmd.CombinedOutput()

	if e == nil {
		uuid = string(b)
		uuid = uuid[4 : len(uuid)-1]
		uuid = strings.ReplaceAll(uuid, "\n", "")
		uuid = strings.ReplaceAll(uuid, " ", "")
		uuid = strings.ReplaceAll(uuid, "\r", "")
	} else {
		return "", nil
	}
	return uuid, nil
}

// Windows:获取磁盘序列号
func getWindowsDiskSN() (diskSN string, err error) {
	cmd := exec.Command("wmic", "diskdrive", "get", "serialnumber")
	b, e := cmd.CombinedOutput()

	if e == nil {
		diskSN = string(b)
		diskSN = diskSN[12 : len(diskSN)-1]
		diskSN = strings.ReplaceAll(diskSN, "\n", "")
		diskSN = strings.ReplaceAll(diskSN, " ", "")
		diskSN = strings.ReplaceAll(diskSN, "\r", "")
	} else {
		return "", nil
	}
	return diskSN, nil
}

// Windows:获取CPUID
func getWindowsCpuId() (cpuId string, err error) {
	var cpuid string
	cmd := exec.Command("wmic", "cpu", "get", "processorid")
	b, e := cmd.CombinedOutput()

	if e == nil {
		cpuid = string(b)
		cpuid = cpuid[12 : len(cpuid)-2]
		cpuid = strings.ReplaceAll(cpuid, "\n", "")
		cpuid = strings.ReplaceAll(cpuid, " ", "")
		cpuid = strings.ReplaceAll(cpuid, "\r", "")
	} else {
		return "", nil
	}
	return cpuid, nil
}

// 获取机器码
// @param fixedcode 参与MD5计算
func GetMachineCode(fixedcode string) string {
	fixedcode = format(fixedcode)
	code := ""
	if runtime.GOOS == "linux" {
		containerId, err := getDockerContainerID()
		if err == nil {
			code = signature(containerId+fixedcode, 8)
		} else {
			sn, _ := getLinuxSerialNumber()
			uuid, _ := getLinuxPlatformUUID()
			diskSN, _ := getLinuxDiskSN()
			cpuId, _ := getLinuxCpuId()
			log.Println(sn, uuid, diskSN, cpuId)
			code = signature(sn+uuid+diskSN+cpuId+fixedcode, 8)
		}
	} else if runtime.GOOS == "windows" {
		sn, _ := getWindowsSerialNumber()
		uuid, _ := getWindowsPlatformUUID()
		diskSN, _ := getWindowsDiskSN()
		cpuId, _ := getWindowsCpuId()
		log.Println(sn, uuid, diskSN, cpuId)
		code = signature(sn+uuid+diskSN+cpuId+fixedcode, 8)
	}

	code = strings.ToUpper(code)
	code = strings.Join([]string{code[:4], code[4:]}, "-")

	return code
}

// 获取mac地址
func getMACAddress() (string, error) {
	netInterfaces, err := net.Interfaces()
	if err != nil {
		return "", err
	}
	var mac string
	for i := 0; i < len(netInterfaces); i++ {
		if (netInterfaces[i].Flags&net.FlagUp) != 0 && (netInterfaces[i].Flags&net.FlagLoopback) == 0 {
			addrs, _ := netInterfaces[i].Addrs()
			for _, address := range addrs {
				ipnet, ok := address.(*net.IPNet)
				if ok && ipnet.IP.IsGlobalUnicast() {
					mac = netInterfaces[i].HardwareAddr.String()
					return mac, nil
				}
			}
		}
	}
	return mac, errors.New("unable to get the correct mac address")
}
